  //Mostly by Ike Feitler
  //With some mystery code left over from an unknown author. 
  //TODO: generalize things more so that not everything depends on 48x48 PImage objects, to make 
  //this easy to adapt to a 48x12 display or other displays.
  
  class Cktalk {
    //Class Cktalk.pde

     //Initial setup and 'data'
    //This is a library for sending UDP packets. 
    //I bet it does other cool stuff too.
    import hypermedia.net.*;
    //An instance of a UDP object with a method for sending packets (I think).
    UDP udp;
    //The packet you send to the Color Kinetics power supply must be 537 bytes for some reason.
    //We say 'new byte[536]' because arrays start counting at 0.  
    byte[] packet = new byte[536];
  
    //The wall I talk to is 48x48 pixels
    //create 48x48 array of pixels
    PImage points = createImage(48,48,RGB);
  
    //The wall is not laid out like a processing PImage though, I need to make
    //a translation map to translate from image pixels to the correct pixels 
    //for the wall.
    
    //create 48x48 array for pixel map index thing
    int[] pixmap = new int[2304];
    
    //The constructor for this class!
    //I use this to take care of some intialization stuff.
    private Cktalk() {  
      udp = new UDP(this, 6038);
      udp.listen( true );
  
      //I don't know why some of these are just 0x04 and why some of them use byte(), but I haven't
      //tried changing it yet. 
      packet[00] = byte(0x04);
      packet[01] = byte(0x01);
      packet[02] = byte(0xdc);
      packet[03] = byte(0x4a);
      packet[04] = byte(0x01);
      packet[06] = byte(0x08);
      packet[07] = byte(0x01);
  
      //The 17th byte of the packet is a number that specifices which the port number/light string
      //This packet of color data is for.
      packet[16] = 2;       // port # string is connected to on the power supply.
      packet[21] = 0x02;
      packet[22] = byte(0xf0); // number of ports, which is 16 for the power supplies I'm talking to.
      packet[23] = byte(0xff);
      //I don't know what the rest of the 1st 24 packets are about; as far as I am concerned they are Secret
      //Color Kinetics Stuff that I don't have to worry about.
      initializemap();
    }
  
    //wallit = Wall It = send it to the wall. Rather specific to the wall I have access to.
    //This procedure takes the 48x48 PImage that you give it, and 
    //sends it off as packets to each of the 16 segments of the top and bottom halves of the 
    //Color Kinetics wall.
    void wallit(PImage dots){
      //top half of wall
      for (int x=1; x < 17; x++){
        packet[16] = byte(x);  
        // fill packet
        for (int i = 0; i < 72; i++) {
          packet[24+(i*3)] = byte(red(dots.pixels[(x-1)*72+i]));
          packet[24+(i*3)+1] = byte(green(dots.pixels[(x-1)*72+i]));
          packet[24+(i*3)+2] = byte(blue(dots.pixels[(x-1)*72+i]));
        }
        udp.send(packet, "192.168.15.221");
      }
      //bottom half of wall
      for (int x=1; x < 17; x++){
        packet[16] = byte(x);  
        // fill packet
        for (int i = 0; i < 72; i++) {
          packet[24+(i*3)] = byte(red(dots.pixels[(x-1)*72+i+1152]));
          packet[24+(i*3)+1] = byte(green(dots.pixels[(x-1)*72+i+1152]));
          packet[24+(i*3)+2] = byte(blue(dots.pixels[(x-1)*72+i+1152]));
        }
        udp.send(packet, "192.168.15.222");
        //all the wonky arithmetic is to keep us properly within the array.
      }
    }
  
    //This fills one segment of the wall. One segment, in this case, 
    //is a 6x72 pixel rectangle that takes up half the tile. Each segment is 
    //like a 6x72 PImage, but the 0,0 coordinate of one half of the square is at
    //what we would call 5,11 in processing, and the other one is at 6,11. So the 
    //segments are mirrored back to back, and to go from our PImage to the one we 
    //send to the wall, we'll have to flip half of them. At least, that's one way to think 
    //about it.
    void arrayseg(int segout, int segin, boolean flip){
      for (int x = 0; x < 6; x++){
        for (int y = 0 ; y < 12; y++){
          if (flip) {
            pixmap[(segout-1)*72+(11-y+12*x)] = (floor(((segin-1)/8))*12 + y)*48+ (((segin-1)%8)*6) + x; 
          } 
          else
            pixmap[(segout-1)*72+(71-12*x-y)] = (floor(((segin-1)/8))*12 + y)*48+ (((segin-1)%8)*6) + x; 
  
        }
      }
  
    }
  
    //initializemap runs arrayseg for each of the segments of the wall, filling
    //each segment of the wall with the proper pixels from the image. 
    //This is completely specific to the 48x48 wall I have access too, but might provide
    //some clues as to how to map other displays.
    void initializemap(){
      //top half of the wall
      //15,16,13,14,5,6,11,12,9,10,3,4,7,8,1,2 
      arrayseg(15,1, false);
      arrayseg(16,2, true);
      arrayseg(13,3, false);
      arrayseg(14,4, true);
      arrayseg(5,5, false);
      arrayseg(6,6, true);
      arrayseg(11,7, false);
      arrayseg(12,8, true);
      arrayseg(9,9, false);
      arrayseg(10,10, true);
      arrayseg(3,11, false);
      arrayseg(4,12, true);
      arrayseg(7,13, false);
      arrayseg(8,14, true);
      arrayseg(1,15, false);
      arrayseg(2,16, true);
      //bottom half of the wall
      //17,18,20,19,25,26,23,24,31,32,29,30,21,22,27,28
      arrayseg(17,17, false);
      arrayseg(18,18, true);
      //this panel is backwards somehow, or something like that. (The 20:19,19:20 thing)
      arrayseg(20,19, false);
      arrayseg(19,20, true);
      arrayseg(25,21, false);
      arrayseg(26,22, true);
      arrayseg(23,23, false);
      arrayseg(24,24, true);
      arrayseg(31,25, false);
      arrayseg(32,26, true);
      arrayseg(29,27, false);
      arrayseg(30,28, true);
      arrayseg(21,29, false);
      arrayseg(22,30, true);
      arrayseg(27,31, false);
      arrayseg(28,32, true);
    }
  
    //This takes the 48x48 PImage and uses the map to look up 
    //the correct position in the image we end up sending to the wall
    //for each of the pixels in the PImage.  This should be pretty efficient,
    //especially compared with running the tranlation
    PImage translate(PImage pixin){
      PImage pixout = createImage(48,48,RGB);
      pixin.loadPixels();
      pixout.loadPixels();
      //just a for loop over all the pixels/the whole array
      for (int x = 0; x < 2304; x++){
        pixout.pixels[x] = pixin.pixels[pixmap[x]];
  
      } 
      pixout.updatePixels();
      return pixout;
    }
  
    //this translates the input 48x48 PImage and sends it to the wall 
    void display(PImage dots){
      wallit(translate(dots)); 
    }
  
    //downsample whatever is on the standard display to a 48x48 PImage for displaying on the wall.
    PImage downsample(){
      PImage blerf = createImage(48,48,RGB);
      loadPixels();
      blerf.loadPixels();
      for (int x = 0; x < 48; x++){
        for (int y = 0; y <48; y++){
          blerf.pixels[y*48+x] = pixels[y*width*floor(height/48)+x*floor(width/48)];
        }
      }
      blerf.updatePixels();
      return blerf;
    }
  
    //move all the pixels over to the effect of scrolling the image down one line
    void scrolldown(PImage dots){
      dots.loadPixels();
      for (int x = 2304; x > 48; x--){
        dots.pixels[x-1]=dots.pixels[x-49];
      } 
      dots.updatePixels(); 
    }
  
    //rotate the 48x48 pixel array 90 degrees Counter-ClockWise (ccw)
    PImage ccw(PImage dots){
      PImage dout = new PImage(48,48,RGB);
      dots.loadPixels(); 
      dout.loadPixels();
      for (int x = 0; x < 48; x++){
        for (int y = 0; y < 48; y++){
          dout.pixels[y+(47-x)*48] = dots.pixels[48*y+x]; 
        }
      }
      dout.updatePixels();
      return dout;
    }
  
    //attempt to cycle pixels in HSB color space
    PImage cycleHSB(PImage dots){
      colorMode(HSB, 255);
      dots.loadPixels();
  
      for (int x = 0; x<2304; x++){
        if ( hue(dots.pixels[x]) < 255 ){
          dots.pixels[x] = color(hue(dots.pixels[x]+7), saturation(dots.pixels[x]), brightness(dots.pixels[x]));
        }
        else 
          dots.pixels[x] = color(0,saturation(dots.pixels[x]),brightness(dots.pixels[x]));
      }
      dots.updatePixels();
      return dots;
    }
  }
  
  
  

